/* 
 * File:   ObjectModel.cpp
 * Author: Pie
 * 
 * Created on April 25, 2011, 4:09 PM
 */

#include "GLee.h"
#include "Scene.h"
#include "ObjectAnimator.h"
#include "../TextureManager.h"
#include "../MathUtil.h"

#define BUFFER_OFFSET(i) ((char *)NULL + (i))

void color_to_float(float f[4], const aiColor4D* c) {
    f[0] = c->r;
    f[1] = c->g;
    f[2] = c->b;
    f[3] = c->a;
}

void zero(float f[4]) {
    f[0] = 0;
    f[1] = 0;
    f[2] = 0;
    f[3] = 0;
}

ObjectNode::ObjectNode(aiNode* node, ObjectNode* parent) {
    _name = node->mName.data;
    _parent = parent;
    copyMatrix(&(node->mTransformation), &_transform);

    _num_mesh = node->mNumMeshes;
    _mesh = new GLushort[_num_mesh];
    for (GLushort i = 0; i < _num_mesh; ++i) {
        _mesh[i] = node->mMeshes[i];
    }
    _num_ch = node->mNumChildren;
    _children = new ObjectNode*[_num_ch];
    for (GLushort i = 0; i < node->mNumChildren; ++i) {
        _children[i] = new ObjectNode(node->mChildren[i], this);
    }
}

ObjectNode::~ObjectNode() {
    //delete _transform;
    delete [] _mesh;
    for (GLushort i = 0; i < _num_ch; ++i) {
        delete _children[i];
    }
    delete [] _children;
}

ObjectBone::ObjectBone(aiBone* bone) {
    _name = bone->mName.data;
    _offset = bone->mOffsetMatrix;

    _num_w = bone->mNumWeights;
    _weights = new aiVertexWeight[_num_w];
    for (GLushort i = 0; i < _num_w; ++i) {
        _weights[i] = bone->mWeights[i];
    }
}

ObjectBone::~ObjectBone() {
    delete [] _weights;
}

ObjectChannel::ObjectChannel(aiNodeAnim* chan) {
    _name = chan->mNodeName.data;
    _pre = chan->mPreState;
    _post = chan->mPostState;
    
    _num_p = chan->mNumPositionKeys;
    _pos_keys = new aiVectorKey[_num_p];
    for (GLushort i = 0; i < _num_p; ++i) {
        _pos_keys[i] = chan->mPositionKeys[i];
    }

    _num_s = chan->mNumScalingKeys;
    _scale_keys = new aiVectorKey[_num_s];
    for (GLushort i = 0; i < _num_s; ++i) {
        _scale_keys[i] = chan->mScalingKeys[i];
    }

    _num_r = chan->mNumRotationKeys;
    _rotate_keys = new aiQuatKey[_num_r];
    for (GLushort i = 0; i < _num_r; ++i) {
        _rotate_keys[i] = chan->mRotationKeys[i];
    }
}
ObjectChannel::~ObjectChannel() {
    delete [] _pos_keys;
    delete [] _scale_keys;
    delete [] _rotate_keys;
}

ObjectAnimation::ObjectAnimation(aiAnimation* anim) {
    _name = anim->mName.data;
    _duration = (GLuint)anim->mDuration;
    _tickps = (GLuint)anim->mTicksPerSecond;
    _num_chan = anim->mNumChannels;
    _chan = new ObjectChannel*[_num_chan];
    for (GLushort i = 0; i < _num_chan; ++i) {
        _chan[i] = new ObjectChannel(anim->mChannels[i]);
    }
}
ObjectAnimation::~ObjectAnimation() {
    for (GLushort i = 0; i < _num_chan; ++i) {
        delete _chan[i];
    }
    delete [] _chan;
}

ObjectVertex::ObjectVertex(aiVector3D* pos, aiVector3D* norm, aiVector3D* uv) :
_norm(0), _uv(0) {
    _pos = new aiVector3D(pos->x, pos->y, pos->z);
    if (norm) _norm = new aiVector3D(norm->x, norm->y, norm->z);
    if (uv) _uv = new aiVector2D(uv->x, uv->y);
}

ObjectVertex::~ObjectVertex() {
    if (_pos) delete _pos;
    if (_norm) delete _norm;
    if (_uv) delete _uv;
}

void ObjectVertex::to_vbo(Vbo* vbo) {
    vbo->x = _pos->x;
    vbo->y = _pos->y;
    vbo->z = _pos->z;
    if (_norm) {
        vbo->nx = _norm->x;
        vbo->ny = _norm->y;
        vbo->nz = _norm->z;
    } else {
        vbo->nx = 0;
        vbo->ny = 1;
        vbo->nz = 0;
    }
    if (_uv) {
        vbo->u = _uv->x;
        vbo->v = _uv->y;
    } else {
        vbo->u = 0;
        vbo->v = 0;
    }
}
ObjectMesh::ObjectMesh(const aiMesh* mesh) :
_mat(0) {
    _name = mesh->mName.data;
    _mat = mesh->mMaterialIndex;
    bool n = mesh->HasNormals();
    bool t = mesh->HasTextureCoords(0);

    _num_v = mesh->mNumVertices;
    _vert = new ObjectVertex*[_num_v];
    for (GLushort i = 0; i < _num_v; ++i) {
        _vert[i] = new ObjectVertex(&(mesh->mVertices[i]),
                (n ? &(mesh->mNormals[i]) : 0),
                (t ? &(mesh->mTextureCoords[0][i]) : 0));
    }

    _num_f = mesh->mNumFaces;
    _face = new GLushort[_num_f*3];
    //TODO: FACE is unnecessary, resolve into mesh
    for (GLushort i = 0; i < _num_f; i++) {
        _face[i*3] = mesh->mFaces[i].mIndices[0];
        if (mesh->mFaces[i].mNumIndices > 1) {
            _face[i*3+1] = mesh->mFaces[i].mIndices[1];
        } else {
            _face[i*3+1] = mesh->mFaces[i].mIndices[0];
        }
        if (mesh->mFaces[i].mNumIndices > 2) {
            _face[i*3+2] = mesh->mFaces[i].mIndices[2];
        } else {
            _face[i*3+2] = mesh->mFaces[i].mIndices[0];
        }
    }

    _num_b = mesh->mNumBones;
    _bone = new ObjectBone*[_num_b];
    for (GLushort i = 0; i < _num_b; ++i) {
        _bone[i] = new ObjectBone(mesh->mBones[i]);
    }
}

ObjectMesh::~ObjectMesh() {
    for (GLushort i = 0; i < _num_v; ++i) {
        delete _vert[i];
    }
    for (GLushort i = 0; i < _num_b; ++i) {
        delete _bone[i];
    }
    delete [] _vert;
    delete [] _face;
    delete [] _bone;
    glDeleteBuffers(1, &_vbuf);
    glDeleteBuffers(1, &_fbuf);
}

void ObjectMesh::build_buffers() {
    Vbo v_data[_num_v];

    for (GLushort i = 0; i < _num_v; ++i) {
        _vert[i]->to_vbo(&v_data[i]);
    }
//    glGenBuffersARB(1, &_vbuf);
//    glBindBufferARB(GL_ARRAY_BUFFER_ARB, _vbuf);
//    glBufferDataARB(GL_ARRAY_BUFFER_ARB, _num_v * sizeof(Vbo), &v_data[0].x, GL_STATIC_DRAW_ARB);
//
//    glGenBuffersARB(1, &_fbuf);
//    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, _fbuf);
//    glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 3 * _num_f * sizeof(GLushort), &_face[0], GL_STATIC_DRAW_ARB);
//
//    glBindBufferARB(GL_ARRAY_BUFFER_ARB, _vbuf);
//    glEnableVertexAttribArray(0);
//    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof (Vbo), BUFFER_OFFSET(0));
//    glEnableVertexAttribArray(1);
//    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof (Vbo), BUFFER_OFFSET(12));
//    glEnableVertexAttribArray(2);
//    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof (Vbo), BUFFER_OFFSET(24));

    glGenBuffers(1, &_vbuf);
    glBindBuffer(GL_ARRAY_BUFFER, _vbuf);
    glBufferData(GL_ARRAY_BUFFER, _num_v * sizeof(Vbo), &v_data[0].x, GL_STATIC_DRAW);

    glGenBuffers(1, &_fbuf);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _fbuf);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * _num_f * sizeof(GLushort), &_face[0], GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, _vbuf);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof (Vbo), BUFFER_OFFSET(0));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof (Vbo), BUFFER_OFFSET(12));
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof (Vbo), BUFFER_OFFSET(24));
}


ObjectMaterial::ObjectMaterial(const aiMaterial* mtl):
_texture(0){

    aiColor4D diff;
    aiColor4D spec;
    aiColor4D amb;
    aiColor4D emis;
    float sh;
    float st;
    aiString path;

    
    if (AI_SUCCESS == mtl->GetTexture(aiTextureType_DIFFUSE, 0, &path)) {
        if (path.length > 0) {
                _texture = TextureManager::getInstance()->get_texture(path.data);
        } else {
            _texture = 0;
        }
    } else {
        _texture = 0;
    }

    if (AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_DIFFUSE, &diff)) {
        color_to_float(_diffuse, &diff);
    } else {
        zero(_diffuse);
    }
    if (AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_SPECULAR, &spec)) {
        color_to_float(_specular, &spec);
    } else {
        zero(_specular);
    }
    if (AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_AMBIENT, &amb)) {
        color_to_float(_ambient, &amb);
    } else {
        zero(_ambient);
    }
    if (AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_EMISSIVE, &emis)) {
        color_to_float(_emission, &emis);
    } else {
        zero(_emission);
    }

    unsigned int max = 1;
    if (AI_SUCCESS == aiGetMaterialFloatArray(mtl, AI_MATKEY_SHININESS, &sh, &max) &&
            AI_SUCCESS == aiGetMaterialFloatArray(mtl, AI_MATKEY_SHININESS_STRENGTH, &st, &max)) {
        _shininess = sh * st;
    } else {
        _shininess = 0;
    }
}

void ObjectMaterial::apply() {
    if (_texture) {
        glBindTexture(GL_TEXTURE_2D, _texture);
    }
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, _diffuse);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, _specular);
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, _ambient);
    glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, _emission);
    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, _shininess);
}

SceneModel::SceneModel(const aiScene* scene):
    _node(0) {
    _num_anim = scene->mNumAnimations;
    _anim = new ObjectAnimation*[_num_anim];
    for (GLushort i = 0; i < _num_anim; ++i) {
        _anim[i] = new ObjectAnimation(scene->mAnimations[i]);
    }
    _num_matl = scene->mNumMaterials;
    _matl = new ObjectMaterial*[_num_matl];
    for (GLushort i = 0; i < _num_matl; ++i) {
        _matl[i] = new ObjectMaterial(scene->mMaterials[i]);
    }
    _num_mesh = scene->mNumMeshes;
    _mesh = new ObjectMesh*[_num_mesh];
    for (GLushort i = 0; i < _num_mesh; ++i) {
        _mesh[i] = new ObjectMesh(scene->mMeshes[i]);
        _mesh[i]->build_buffers();
    }
    _node = new ObjectNode(scene->mRootNode);
}

SceneModel::~SceneModel() {
    delete _node;
    for (GLushort i = 0; i < _num_anim; ++i) {
        delete _anim[i];
    }
    delete [] _anim;
    for (GLushort i = 0; i < _num_matl; ++i) {
        delete _matl[i];
    }
    delete [] _matl;
    for (GLushort i = 0; i < _num_mesh; ++i) {
        delete _mesh[i];
    }
    delete [] _mesh;
}

SceneNode::SceneNode() {
    _parent = 0;
    _scene = 0;
    _anim = 0;
}

SceneNode::~SceneNode() {
    delete _scene;
    delete _anim;
    for (vector<SceneNode*>::iterator it = _children.begin(); it != _children.end(); it++) {
        delete *it;
    }
}

void SceneNode::load(SceneModel* scene) {
    _scene = scene;
}

void SceneNode::add_child(SceneNode* child) {
    _children.push_back(child);
    child->set_parent(this);
}

void SceneNode::set_parent(SceneNode* parent) {
    _parent = parent;
}

void SceneNode::_build() {

}

void _node_draw(SceneModel* scene, ObjectNode* node) {
    glPushMatrix();
    aiMatrix4x4 mat = node->_transform;
    mat.Transpose();
    glMultMatrixf((GLfloat*)&mat);
    for (GLushort i = 0; i < node->_num_mesh; i++) {
        //bind the material for the given mesh
        ObjectMesh* m = scene->_mesh[node->_mesh[i]];
        scene->_matl[m->_mat]->apply();

//            glBindBufferARB(GL_ARRAY_BUFFER_ARB, m->_vbuf);
//            glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, m->_fbuf);
        glBindBuffer(GL_ARRAY_BUFFER, m->_vbuf);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->_fbuf);

        glDrawRangeElements(GL_TRIANGLES, 0, m->_num_v, m->_num_f * 3, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));
    }
    for (GLushort i = 0; i < node->_num_ch; ++i) {
        _node_draw(scene, node->_children[i]);
    }
    glPopMatrix();

}
void SceneNode::input(SDL_Event* evt, unsigned int ms) {}
void SceneNode::update(unsigned int ms) {}
void SceneNode::draw() {
    if (_scene){

        _node_draw(_scene, _scene->_node);

//        glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
//        glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    }
    for (vector<SceneNode*>::iterator it = _children.begin(); it != _children.end(); it++) {
        (*it)->draw();
    }
}
